OpenTTD Source 20260421-master-gc2fbc6fdeb
yapf_ship_regions.cpp
Go to the documentation of this file.
1/*
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 <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
6 */
7
9
10#include "../../stdafx.h"
11#include "../../ship.h"
12
13#include "yapf.hpp"
14#include "yapf_ship_regions.h"
15#include "../water_regions.h"
16
17#include "../../safeguards.h"
18
19static constexpr int DIRECT_NEIGHBOUR_COST = 100;
20static constexpr int NODES_PER_REGION = 4;
21static constexpr int MAX_NUMBER_OF_NODES = 65536;
22
23static constexpr int NODE_LIST_HASH_BITS_OPEN = 12;
24static constexpr int NODE_LIST_HASH_BITS_CLOSED = 12;
25
28 WaterRegionPatchDesc water_region_patch;
29
30 inline void Set(const WaterRegionPatchDesc &water_region_patch)
31 {
32 this->water_region_patch = water_region_patch;
33 }
34
35 inline int CalcHash() const { return CalculateWaterRegionPatchHash(this->water_region_patch); }
36 inline bool operator==(const WaterRegionPatchKey &other) const { return this->CalcHash() == other.CalcHash(); }
37};
38
39inline uint ManhattanDistance(const WaterRegionPatchKey &a, const WaterRegionPatchKey &b)
40{
41 return (std::abs(a.water_region_patch.x - b.water_region_patch.x) + std::abs(a.water_region_patch.y - b.water_region_patch.y)) * DIRECT_NEIGHBOUR_COST;
42}
43
45struct WaterRegionNode : CYapfNodeT<WaterRegionPatchKey, WaterRegionNode> {
46 using Key = WaterRegionPatchKey;
47 using Node = WaterRegionNode;
48
49 inline void Set(Node *parent, const WaterRegionPatchDesc &water_region_patch)
50 {
51 this->key.Set(water_region_patch);
52 this->hash_next = nullptr;
53 this->parent = parent;
54 this->cost = 0;
55 this->estimate = 0;
56 }
57
58 inline void Set(Node *parent, const Key &key)
59 {
60 this->Set(parent, key.water_region_patch);
61 }
62
63 DiagDirection GetDiagDirFromParent() const
64 {
65 if (this->parent == nullptr) return INVALID_DIAGDIR;
66 const int dx = this->key.water_region_patch.x - this->parent->key.water_region_patch.x;
67 const int dy = this->key.water_region_patch.y - this->parent->key.water_region_patch.y;
68 if (dx > 0 && dy == 0) return DIAGDIR_SW;
69 if (dx < 0 && dy == 0) return DIAGDIR_NE;
70 if (dx == 0 && dy > 0) return DIAGDIR_SE;
71 if (dx == 0 && dy < 0) return DIAGDIR_NW;
72 return INVALID_DIAGDIR;
73 }
74};
75
77
78/* We don't need a follower but YAPF requires one. */
79struct WaterRegionFollower : public CFollowTrackWater {};
80
81class YapfShipRegions;
82
85 using Tpf = YapfShipRegions;
86 using TrackFollower = WaterRegionFollower;
87 using NodeList = WaterRegionNodeList;
88 using VehicleType = Ship;
89};
90
92class YapfShipRegions
93 : public CYapfBaseT<WaterRegionTypes>
94 , public CYapfSegmentCostCacheNoneT<WaterRegionTypes>
95{
96private:
97 using Node = typename WaterRegionTypes::NodeList::Item;
98
99 std::vector<WaterRegionPatchKey> origin_keys;
101
102 inline YapfShipRegions &Yapf()
103 {
104 return *static_cast<YapfShipRegions *>(this);
105 }
106
107public:
108 explicit YapfShipRegions(int max_nodes)
109 {
110 this->max_search_nodes = max_nodes;
111 }
112
113 void AddOrigin(const WaterRegionPatchDesc &water_region_patch)
114 {
115 if (water_region_patch.label == INVALID_WATER_REGION_PATCH) return;
116 if (!HasOrigin(water_region_patch)) {
117 this->origin_keys.emplace_back(water_region_patch);
118 Node &node = Yapf().CreateNewNode();
119 node.Set(nullptr, water_region_patch);
120 Yapf().AddStartupNode(node);
121 }
122 }
123
124 bool HasOrigin(const WaterRegionPatchDesc &water_region_patch)
125 {
126 return std::ranges::find(this->origin_keys, WaterRegionPatchKey{ water_region_patch }) != this->origin_keys.end();
127 }
128
129 void SetDestination(const WaterRegionPatchDesc &water_region_patch)
130 {
131 this->dest.Set(water_region_patch);
132 }
133
135 inline void PfFollowNode(Node &old_node)
136 {
137 VisitWaterRegionPatchCallback visit_func = [&](const WaterRegionPatchDesc &water_region_patch) {
138 Node &node = Yapf().CreateNewNode();
139 node.Set(&old_node, water_region_patch);
140 Yapf().AddNewNode(node, TrackFollower{});
141 };
142 VisitWaterRegionPatchNeighbours(old_node.key.water_region_patch, visit_func);
143 }
144
146 inline bool PfDetectDestination(Node &n) const
147 {
148 return n.key == this->dest;
149 }
150
152 inline bool PfCalcCost(Node &n, [[maybe_unused]] const TrackFollower *follower)
153 {
154 n.cost = n.parent->cost + ManhattanDistance(n.key, n.parent->key);
155
156 /* Incentivise zigzagging by adding a slight penalty when the search continues in the same direction. */
157 Node *grandparent = n.parent->parent;
158 if (grandparent != nullptr) {
159 const DiagDirDiff dir_diff = DiagDirDifference(n.parent->GetDiagDirFromParent(), n.GetDiagDirFromParent());
160 if (dir_diff != DIAGDIRDIFF_90LEFT && dir_diff != DIAGDIRDIFF_90RIGHT) n.cost += 1;
161 }
162
163 return true;
164 }
165
167 inline bool PfCalcEstimate(Node &n)
168 {
169 if (this->PfDetectDestination(n)) {
170 n.estimate = n.cost;
171 return true;
172 }
173
174 n.estimate = n.cost + ManhattanDistance(n.key, this->dest);
175
176 return true;
177 }
178
180 inline char TransportTypeChar() const { return '^'; }
181
183 static std::vector<WaterRegionPatchDesc> FindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length)
184 {
185 const WaterRegionPatchDesc start_water_region_patch = GetWaterRegionPatchInfo(start_tile);
186
187 /* We reserve 4 nodes (patches) per water region. The vast majority of water regions have 1 or 2 regions so this should be a pretty
188 * safe limit. We cap the limit at 65536 which is at a region size of 16x16 is equivalent to one node per region for a 4096x4096 map. */
189 const int node_limit = std::min(static_cast<int>(Map::Size() * NODES_PER_REGION) / WATER_REGION_NUMBER_OF_TILES, MAX_NUMBER_OF_NODES);
190 YapfShipRegions pf(node_limit);
191 pf.SetDestination(start_water_region_patch);
192
193 if (v->current_order.IsType(OT_GOTO_STATION)) {
194 StationID station_id = v->current_order.GetDestination().ToStationID();
195 const BaseStation *station = BaseStation::Get(station_id);
196 for (const auto &tile : station->GetTileArea(StationType::Dock)) {
197 if (IsDockingTile(tile) && IsShipDestinationTile(tile, station_id)) {
198 pf.AddOrigin(GetWaterRegionPatchInfo(tile));
199 }
200 }
201 } else {
202 TileIndex tile = v->dest_tile == INVALID_TILE ? TileIndex{} : v->dest_tile;
203 pf.AddOrigin(GetWaterRegionPatchInfo(tile));
204 }
205
206 /* If origin and destination are the same we simply return that water patch. */
207 std::vector<WaterRegionPatchDesc> path = { start_water_region_patch };
208 path.reserve(max_returned_path_length);
209 if (pf.HasOrigin(start_water_region_patch)) return path;
210
211 /* Find best path. */
212 if (!pf.FindPath(v)) return {}; // Path not found.
213
214 Node *node = pf.GetBestNode();
215 for (int i = 0; i < max_returned_path_length - 1; ++i) {
216 if (node != nullptr) {
217 node = node->parent;
218 if (node != nullptr) path.push_back(node->key.water_region_patch);
219 }
220 }
221
222 assert(!path.empty());
223 return path;
224 }
225};
226
234std::vector<WaterRegionPatchDesc> YapfShipFindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length)
235{
236 return YapfShipRegions::FindWaterRegionPath(v, start_tile, max_returned_path_length);
237}
void AddNewNode(Node &n, const TrackFollower &follower)
AddNewNode() - called by Tderived::PfFollowNode() for each child node.
Node * GetBestNode()
If path was found return the best node that has reached the destination.
bool FindPath(const VehicleType *v)
Main pathfinder routine:
Node & CreateNewNode()
Calls NodeList::CreateNewNode() - allocates new node that can be filled and used as argument for AddS...
void AddStartupNode(Node &n)
Add new node (created by CreateNewNode and filled with data) into open list.
CYapfSegmentCostCacheNoneT - the formal only yapf cost cache provider that implements PfNodeCacheFetc...
Hash table based node list multi-container class.
Definition nodelist.hpp:21
Water region based YAPF implementation for ships.
bool PfCalcEstimate(Node &n)
Called by YAPF to calculate cost estimate.
static std::vector< WaterRegionPatchDesc > FindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length)
Finds a path at the water region level.
bool PfCalcCost(Node &n, const TrackFollower *follower)
Called by YAPF to calculate the cost from the origin to the given node.
bool PfDetectDestination(Node &n) const
Called by YAPF to detect if node ends in the desired destination.
void PfFollowNode(Node &old_node)
Called by YAPF to move from the given node to the next tile.
char TransportTypeChar() const
Return debug report character to identify the transportation type.
DiagDirDiff DiagDirDifference(DiagDirection d0, DiagDirection d1)
Calculate the difference between two DiagDirection values.
DiagDirDiff
Enumeration for the difference between to DiagDirection.
@ DIAGDIRDIFF_90RIGHT
90 degrees right
@ DIAGDIRDIFF_90LEFT
90 degrees left
DiagDirection
Enumeration for diagonal directions.
@ DIAGDIR_NE
Northeast, upper right on your monitor.
@ DIAGDIR_NW
Northwest.
@ DIAGDIR_SE
Southeast.
@ INVALID_DIAGDIR
Flag for an invalid DiagDirection.
@ DIAGDIR_SW
Southwest.
A number of safeguards to prevent using unsafe methods.
Base for ships.
bool IsShipDestinationTile(TileIndex tile, StationID station)
Test if a tile is a docking tile for the given station.
Definition ship_cmd.cpp:618
@ Dock
Ship port.
Definition of base types and functions in a cross-platform compatible way.
Base class for all station-ish types.
virtual TileArea GetTileArea(StationType type) const =0
Get the tile area for a given station type.
Yapf Node base.
Definition yapf_node.hpp:61
static uint Size()
Get the size of the map.
Definition map_func.h:280
DestinationID GetDestination() const
Gets the destination of this order.
Definition order_base.h:100
bool IsType(OrderType type) const
Check whether this order is of the given type.
Definition order_base.h:67
static BaseStation * Get(auto index)
All ships have this type.
Definition ship.h:32
Order current_order
The current order (+ status, like: loading).
TileIndex dest_tile
Heading for this tile.
Yapf Node for water regions.
Describes a single interconnected patch of water within a particular water region.
int y
The Y coordinate of the water region, i.e. Y=2 is the 3rd water region along the Y-axis.
int x
The X coordinate of the water region, i.e. X=2 is the 3rd water region along the X-axis.
WaterRegionPatchLabel label
Unique label identifying the patch within the region.
Yapf Node Key that represents a single patch of interconnected water within a water region.
Types struct required for YAPF internals.
StrongType::Typedef< uint32_t, struct TileIndexTag, StrongType::Compare, StrongType::Integer, StrongType::Compatible< int32_t >, StrongType::Compatible< int64_t > > TileIndex
The index/ID of a Tile.
Definition tile_type.h:92
constexpr TileIndex INVALID_TILE
The very nice invalid tile marker.
Definition tile_type.h:100
bool IsDockingTile(Tile t)
Checks whether the tile is marked as a dockling tile.
Definition water_map.h:375
WaterRegionPatchDesc GetWaterRegionPatchInfo(TileIndex tile)
Returns basic water region patch information for the provided tile.
int CalculateWaterRegionPatchHash(const WaterRegionPatchDesc &water_region_patch)
Calculates a number that uniquely identifies the provided water region patch.
void VisitWaterRegionPatchNeighbours(const WaterRegionPatchDesc &water_region_patch, VisitWaterRegionPatchCallback &callback)
Calls the provided callback function on all accessible water region patches in each cardinal directio...
Handles dividing the water in the map into regions to assist pathfinding.
Base includes/functions for YAPF.
std::vector< WaterRegionPatchDesc > YapfShipFindWaterRegionPath(const Ship *v, TileIndex start_tile, int max_returned_path_length)
Finds a path at the water region level.
Implementation of YAPF for water regions, which are used for finding intermediate ship destinations.