2 * This file is part of OpenTTD.
3 * 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.
4 * 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.
5 * 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/>.
8 /** @file yapf_base.hpp Base classes for YAPF. */
13 #include "../../debug.h"
14 #include "../../settings_type.h"
15 #include "../../misc/dbg_helpers.h"
16 #include "yapf_type.hpp"
19 * CYapfBaseT - A-star type path finder base class.
20 * Derive your own pathfinder from it. You must provide the following template argument:
21 * Types - used as collection of local types used in pathfinder
23 * Requirements for the Types struct:
24 * ----------------------------------
25 * The following types must be defined in the 'Types' argument:
26 * - Types::Tpf - your pathfinder derived from CYapfBaseT
27 * - Types::NodeList - open/closed node list (look at CNodeList_HashTableT)
28 * NodeList needs to have defined local type Titem - defines the pathfinder node type.
29 * Node needs to define local type Key - the node key in the collection ()
31 * For node list you can use template class NodeList, for which
32 * you need to declare only your node type. Look at test_yapf.h for an example.
35 * Requirements to your pathfinder class derived from CYapfBaseT:
36 * --------------------------------------------------------------
37 * Your pathfinder derived class needs to implement following methods:
38 * inline void PfSetStartupNodes()
39 * inline void PfFollowNode(Node &org)
40 * inline bool PfCalcCost(Node &n)
41 * inline bool PfCalcEstimate(Node &n)
42 * inline bool PfDetectDestination(Node &n)
44 * For more details about those methods, look at the end of CYapfBaseT
45 * declaration. There are some examples. For another example look at
46 * test_yapf.h (part or unittest project).
48 template <class Types
>
51 typedef typename
Types::Tpf Tpf
; ///< the pathfinder class (derived from THIS class)
52 typedef typename
Types::TrackFollower TrackFollower
;
53 typedef typename
Types::NodeList NodeList
; ///< our node list
54 typedef typename
Types::VehicleType VehicleType
; ///< the type of vehicle
55 typedef typename
NodeList::Item Node
; ///< this will be our node type
56 typedef typename
Node::Key Key
; ///< key to hash tables
58 NodeList nodes
; ///< node list multi-container
61 Node
*best_dest_node
= nullptr; ///< pointer to the destination node found at last round
62 Node
*best_intermediate_node
= nullptr; ///< here should be node closest to the destination if path not found
63 const YAPFSettings
*settings
; ///< current settings (_settings_game.yapf)
64 int max_search_nodes
; ///< maximum number of nodes we are allowed to visit before we give up
65 const VehicleType
*vehicle
= nullptr; ///< vehicle that we are trying to drive
67 int stats_cost_calcs
= 0; ///< stats - how many node's costs were calculated
68 int stats_cache_hits
= 0; ///< stats - how many node's costs were reused from cache
71 int num_steps
= 0; ///< this is there for debugging purposes (hope it doesn't hurt)
74 /** default constructor */
75 inline CYapfBaseT() : settings(&_settings_game
.pf
.yapf
), max_search_nodes(PfGetSettings().max_search_nodes
) {}
77 /** default destructor */
81 /** to access inherited path finder */
84 return *static_cast<Tpf
*>(this);
88 /** return current settings (can be custom - company based - but later) */
89 inline const YAPFSettings
&PfGetSettings() const
91 return *this->settings
;
95 * Main pathfinder routine:
96 * - set startup node(s)
97 * - main loop that stops if:
98 * - the destination was found
99 * - or the open list is empty (no route to destination).
100 * - or the maximum amount of loops reached - max_search_nodes (default = 10000)
101 * @return true if the path was found
103 inline bool FindPath(const VehicleType
*v
)
107 Yapf().PfSetStartupNodes();
111 Node
*best_open_node
= this->nodes
.GetBestOpenNode();
112 if (best_open_node
== nullptr) break;
114 if (Yapf().PfDetectDestination(*best_open_node
)) {
115 this->best_dest_node
= best_open_node
;
119 Yapf().PfFollowNode(*best_open_node
);
120 if (this->max_search_nodes
!= 0 && this->nodes
.ClosedCount() >= this->max_search_nodes
) break;
122 this->nodes
.PopOpenNode(best_open_node
->GetKey());
123 this->nodes
.InsertClosedNode(*best_open_node
);
126 const bool destination_found
= (this->best_dest_node
!= nullptr);
128 if (_debug_yapf_level
>= 3) {
129 const UnitID veh_idx
= (this->vehicle
!= nullptr) ? this->vehicle
->unitnumber
: 0;
130 const char ttc
= Yapf().TransportTypeChar();
131 const float cache_hit_ratio
= (this->stats_cache_hits
== 0) ? 0.0f
: ((float)this->stats_cache_hits
/ (float)(this->stats_cache_hits
+ this->stats_cost_calcs
) * 100.0f
);
132 const int cost
= destination_found
? this->best_dest_node
->cost
: -1;
133 const int dist
= destination_found
? this->best_dest_node
->estimate
- this->best_dest_node
->cost
: -1;
135 Debug(yapf
, 3, "[YAPF{}]{}{:4d} - {} rounds - {} open - {} closed - CHR {:4.1f}% - C {} D {}",
136 ttc
, destination_found
? '-' : '!', veh_idx
, this->num_steps
, this->nodes
.OpenCount(), this->nodes
.ClosedCount(), cache_hit_ratio
, cost
, dist
140 return destination_found
;
144 * If path was found return the best node that has reached the destination. Otherwise
145 * return the best visited node (which was nearest to the destination).
147 inline Node
*GetBestNode()
149 return (this->best_dest_node
!= nullptr) ? this->best_dest_node
: this->best_intermediate_node
;
153 * Calls NodeList::CreateNewNode() - allocates new node that can be filled and used
154 * as argument for AddStartupNode() or AddNewNode()
156 inline Node
&CreateNewNode()
158 Node
&node
= this->nodes
.CreateNewNode();
162 /** Add new node (created by CreateNewNode and filled with data) into open list */
163 inline void AddStartupNode(Node
&n
)
165 Yapf().PfNodeCacheFetch(n
);
166 /* insert the new node only if it is not there */
167 if (this->nodes
.FindOpenNode(n
.key
) == nullptr) {
168 this->nodes
.InsertOpenNode(n
);
170 /* if we are here, it means that node is already there - how it is possible?
171 * probably the train is in the position that both its ends point to the same tile/exit-dir
172 * very unlikely, but it happened */
176 /** add multiple nodes - direct children of the given node */
177 inline void AddMultipleNodes(Node
*parent
, const TrackFollower
&tf
)
179 bool is_choice
= (KillFirstBit(tf
.new_td_bits
) != TRACKDIR_BIT_NONE
);
180 for (TrackdirBits rtds
= tf
.new_td_bits
; rtds
!= TRACKDIR_BIT_NONE
; rtds
= KillFirstBit(rtds
)) {
181 Trackdir td
= (Trackdir
)FindFirstBit(rtds
);
182 Node
&n
= Yapf().CreateNewNode();
183 n
.Set(parent
, tf
.new_tile
, td
, is_choice
);
184 Yapf().AddNewNode(n
, tf
);
189 * In some cases an intermediate node branch should be pruned.
190 * The most prominent case is when a red EOL signal is encountered, but
191 * there was a segment change (e.g. a rail type change) before that. If
192 * the branch would not be pruned, the rail type change location would
193 * remain the best intermediate node, and thus the vehicle would still
194 * go towards the red EOL signal.
196 void PruneIntermediateNodeBranch(Node
*n
)
198 bool intermediate_on_branch
= false;
199 while (n
!= nullptr && (n
->segment
->end_segment_reason
& ESRB_CHOICE_FOLLOWS
) == 0) {
200 if (n
== Yapf().best_intermediate_node
) intermediate_on_branch
= true;
203 if (intermediate_on_branch
) Yapf().best_intermediate_node
= n
;
207 * AddNewNode() - called by Tderived::PfFollowNode() for each child node.
208 * Nodes are evaluated here and added into open list
210 void AddNewNode(Node
&n
, const TrackFollower
&tf
)
212 /* evaluate the node */
213 bool cached
= Yapf().PfNodeCacheFetch(n
);
215 this->stats_cost_calcs
++;
217 this->stats_cache_hits
++;
220 bool valid
= Yapf().PfCalcCost(n
, &tf
);
222 if (valid
) valid
= Yapf().PfCalcEstimate(n
);
224 /* have the cost or estimate callbacks marked this node as invalid? */
227 /* The new node can be set as the best intermediate node only once we're
228 * certain it will be finalized by being inserted into the open list. */
229 bool set_intermediate
= this->max_search_nodes
> 0 && (this->best_intermediate_node
== nullptr || (this->best_intermediate_node
->GetCostEstimate() - this->best_intermediate_node
->GetCost()) > (n
.GetCostEstimate() - n
.GetCost()));
231 /* check new node against open list */
232 Node
*open_node
= this->nodes
.FindOpenNode(n
.GetKey());
233 if (open_node
!= nullptr) {
234 /* another node exists with the same key in the open list
235 * is it better than new one? */
236 if (n
.GetCostEstimate() < open_node
->GetCostEstimate()) {
237 /* update the old node by value from new one */
238 this->nodes
.PopOpenNode(n
.GetKey());
240 /* add the updated old node back to open list */
241 this->nodes
.InsertOpenNode(*open_node
);
242 if (set_intermediate
) this->best_intermediate_node
= open_node
;
247 /* check new node against closed list */
248 Node
*closed_node
= this->nodes
.FindClosedNode(n
.GetKey());
249 if (closed_node
!= nullptr) {
250 /* another node exists with the same key in the closed list
251 * is it better than new one? */
252 int node_est
= n
.GetCostEstimate();
253 int closed_est
= closed_node
->GetCostEstimate();
254 if (node_est
< closed_est
) {
255 /* If this assert occurs, you have probably problem in
256 * your Tderived::PfCalcCost() or Tderived::PfCalcEstimate().
257 * The problem could be:
258 * - PfCalcEstimate() gives too large numbers
259 * - PfCalcCost() gives too small numbers
260 * - You have used negative cost penalty in some cases (cost bonus) */
265 /* the new node is really new
266 * add it to the open list */
267 this->nodes
.InsertOpenNode(n
);
268 if (set_intermediate
) this->best_intermediate_node
= &n
;
271 const VehicleType
* GetVehicle() const
273 return this->vehicle
;
276 void DumpBase(DumpTarget
&dmp
) const
278 dmp
.WriteStructT("nodes", &this->nodes
);
279 dmp
.WriteValue("num_steps", this->num_steps
);
283 #endif /* YAPF_BASE_HPP */