1
0
Fork 0

Fix #13105, Fix #12333: Ship pathfinder always considers reversing

pull/13171/head
Koen Bussemaker 2024-12-13 22:00:42 +01:00
parent 5d8e98207d
commit d4d3c7def9
4 changed files with 88 additions and 113 deletions

View File

@ -20,11 +20,10 @@
/**
* Finds the best path for given ship using YAPF.
* @param v the ship that needs to find a path
* @param tile the tile to find the path from (should be next tile the ship is about to enter)
* @param path_found [out] Whether a path has been found (true) or has been guessed (false)
* @return the best trackdir for next turn or INVALID_TRACK if the path could not be found
* @return the best trackdir for next turn. This includes potential reverse directions.
*/
Track YapfShipChooseTrack(const Ship *v, TileIndex tile, bool &path_found, ShipPathCache &path_cache);
Trackdir YapfShipChooseTrack(const Ship *v, bool &path_found, ShipPathCache &path_cache);
/**
* Returns true if it is better to reverse the ship before leaving depot using YAPF.
@ -32,7 +31,7 @@ Track YapfShipChooseTrack(const Ship *v, TileIndex tile, bool &path_found, ShipP
* @param trackdir [out] the best of all possible reversed trackdirs
* @return true if reversing is better
*/
bool YapfShipCheckReverse(const Ship *v, Trackdir *trackdir);
bool YapfShipCheckReverse(const Ship *v, Trackdir &trackdir);
/**
* Finds the best path for given road vehicle using YAPF.

View File

@ -25,7 +25,9 @@ public:
protected:
TileIndex origin_tile; ///< origin tile
TrackdirBits origin_trackdirs; ///< origin trackdir mask
TrackdirBits origin_forward_trackdirs; ///< trackdirs considered for forward motion
TrackdirBits origin_reverse_trackdirs; ///< trackdirs considered for reversal
int reverse_penalty; ///< Cost penalty added for the reverse directions
/** to access inherited path finder */
inline Tpf &Yapf()
@ -35,21 +37,29 @@ protected:
public:
/** Set origin tile / trackdir mask */
void SetOrigin(TileIndex tile, TrackdirBits trackdirs)
void SetOrigin(TileIndex tile, TrackdirBits forward_trackdirs, TrackdirBits reverse_trackdirs = TRACKDIR_BIT_NONE, int reverse_penalty = 0)
{
assert((forward_trackdirs & reverse_trackdirs) == TRACKDIR_BIT_NONE); // Forward dirs can't also be reverse dirs and vice versa
this->origin_tile = tile;
this->origin_trackdirs = trackdirs;
this->origin_forward_trackdirs = forward_trackdirs;
this->origin_reverse_trackdirs = reverse_trackdirs;
this->reverse_penalty = reverse_penalty;
}
/** Called when YAPF needs to place origin nodes into open list */
void PfSetStartupNodes()
{
bool is_choice = (KillFirstBit(this->origin_trackdirs) != TRACKDIR_BIT_NONE);
for (TrackdirBits tdb = this->origin_trackdirs; tdb != TRACKDIR_BIT_NONE; tdb = KillFirstBit(tdb)) {
Trackdir td = (Trackdir)FindFirstBit(tdb);
Node &n1 = Yapf().CreateNewNode();
n1.Set(nullptr, this->origin_tile, td, is_choice);
Yapf().AddStartupNode(n1);
const bool is_choice = CountBits(this->origin_forward_trackdirs | this->origin_reverse_trackdirs) > 1;
for (Trackdir td : SetTrackdirBitIterator(this->origin_forward_trackdirs)) {
Node &node = Yapf().CreateNewNode();
node.Set(nullptr, this->origin_tile, td, is_choice);
Yapf().AddStartupNode(node);
}
for (Trackdir td : SetTrackdirBitIterator(this->origin_reverse_trackdirs)) {
Node &node = Yapf().CreateNewNode();
node.Set(nullptr, this->origin_tile, td, is_choice);
node.cost = reverse_penalty;
Yapf().AddStartupNode(node);
}
}
};

View File

@ -20,7 +20,7 @@
constexpr int NUMBER_OR_WATER_REGIONS_LOOKAHEAD = 4;
constexpr int MAX_SHIP_PF_NODES = (NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1) * WATER_REGION_NUMBER_OF_TILES * 4; // 4 possible exit dirs per tile.
constexpr int SHIP_REVERSE_PENALTY = 10 * YAPF_TILE_LENGTH;
constexpr int SHIP_LOST_PATH_LENGTH = 8; // The length of the (aimless) path assigned when a ship is lost.
template <class Types>
@ -178,7 +178,7 @@ public:
return FindFirstTrackdir(trackdirs);
}
/** Returns a random tile/trackdir that can be reached from the current tile/trackdir, or tile/INVALID_TRACK if none is available. */
/** Returns a random tile/trackdir that can be reached from the current tile/trackdir, or tile/INVALID_TRACKDIR if none is available. */
static std::pair<TileIndex, Trackdir> GetRandomFollowUpTileTrackdir(const Ship *v, TileIndex tile, Trackdir dir)
{
TrackFollower follower(v);
@ -198,10 +198,10 @@ public:
for (int i = 0; i < path_length; ++i) {
tile_dir = GetRandomFollowUpTileTrackdir(v, tile_dir.first, tile_dir.second);
if (tile_dir.second == INVALID_TRACKDIR) break;
path_cache.push_back(tile_dir.second);
path_cache.emplace_back(tile_dir.second);
}
if (path_cache.empty()) return INVALID_TRACKDIR;
if (path_cache.empty()) return ReverseTrackdir(v->GetVehicleTrackdir());
/* Reverse the path so we can take from the end. */
std::reverse(std::begin(path_cache), std::end(path_cache));
@ -211,10 +211,15 @@ public:
return result;
}
static Trackdir ChooseShipTrack(const Ship *v, TileIndex tile, TrackdirBits forward_dirs, TrackdirBits reverse_dirs,
bool &path_found, ShipPathCache &path_cache, Trackdir &best_origin_dir)
static Trackdir ChooseShipTrack(const Ship *v, TrackdirBits forward_dirs, TrackdirBits reverse_dirs,
bool &path_found, ShipPathCache &path_cache)
{
const std::vector<WaterRegionPatchDesc> high_level_path = YapfShipFindWaterRegionPath(v, tile, NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1);
assert(HasBit(forward_dirs, v->GetVehicleTrackdir()));
assert(HasBit(reverse_dirs, ReverseTrackdir(v->GetVehicleTrackdir())));
assert((forward_dirs & reverse_dirs) == TRACKDIR_BIT_NONE);
assert((TrackdirReachesTrackdirs(v->GetVehicleTrackdir()) & reverse_dirs) == TRACKDIR_BIT_NONE);
const std::vector<WaterRegionPatchDesc> high_level_path = YapfShipFindWaterRegionPath(v, v->tile, NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1);
if (high_level_path.empty()) {
path_found = false;
/* Make the ship move around aimlessly. This prevents repeated pathfinder calls and clearly indicates that the ship is lost. */
@ -228,7 +233,7 @@ public:
Tpf pf(MAX_SHIP_PF_NODES);
/* Set origin and destination nodes */
pf.SetOrigin(v->tile, forward_dirs | reverse_dirs);
pf.SetOrigin(v->tile, forward_dirs, reverse_dirs, SHIP_REVERSE_PENALTY);
pf.SetDestination(v);
const bool is_intermediate_destination = static_cast<int>(high_level_path.size()) >= NUMBER_OR_WATER_REGIONS_LOOKAHEAD + 1;
if (is_intermediate_destination) pf.SetIntermediateDestination(high_level_path.back());
@ -249,17 +254,16 @@ public:
* to the final destination tile. The low-level pathfinder might actually prefer a different docking tile in a nearby region. Without
* caching the full path the ship can get stuck in a loop. */
const WaterRegionPatchDesc end_water_patch = GetWaterRegionPatchInfo(node->GetTile());
assert(GetWaterRegionPatchInfo(tile) == high_level_path.front());
const WaterRegionPatchDesc start_water_patch = high_level_path.front();
assert(GetWaterRegionPatchInfo(v->tile) == high_level_path.front());
const WaterRegionPatchDesc start_patch = high_level_path.front();
while (node->parent) {
const WaterRegionPatchDesc node_water_patch = GetWaterRegionPatchInfo(node->GetTile());
const bool node_water_patch_on_high_level_path = std::ranges::find(high_level_path, node_water_patch) != high_level_path.end();
const bool add_full_path = !is_intermediate_destination && node_water_patch != end_water_patch;
const WaterRegionPatchDesc current_patch = GetWaterRegionPatchInfo(node->parent->GetTile());
const bool current_patch_on_high_level_path = std::ranges::find(high_level_path, current_patch) != high_level_path.end();
const bool add_full_path = !is_intermediate_destination && current_patch != end_water_patch;
/* The cached path must always lead to a region patch that's on the high level path.
* This is what can happen when that's not the case https://github.com/OpenTTD/OpenTTD/issues/12176. */
if (add_full_path || !node_water_patch_on_high_level_path || node_water_patch == start_water_patch) {
if (add_full_path || !current_patch_on_high_level_path || current_patch == start_patch) {
path_cache.push_back(node->GetTrackdir());
} else {
path_cache.clear();
@ -268,11 +272,10 @@ public:
}
assert(node->GetTile() == v->tile);
/* Return INVALID_TRACKDIR to trigger a ship reversal if that is the best option. */
best_origin_dir = node->GetTrackdir();
if ((TrackdirToTrackdirBits(best_origin_dir) & forward_dirs) == TRACKDIR_BIT_NONE) {
/* Return a reverse direction if that is the best option. */
if (HasBit(reverse_dirs, node->GetTrackdir())) {
path_cache.clear();
return INVALID_TRACKDIR;
return node->GetTrackdir();
}
/* A empty path means we are already at the destination. The pathfinder shouldn't have been called at all.
@ -284,42 +287,12 @@ public:
path_cache.pop_back();
/* Clear path cache when in final water region patch. This is to allow ships to spread over different docking tiles dynamically. */
if (start_water_patch == end_water_patch) path_cache.clear();
if (start_patch == end_water_patch) path_cache.clear();
return result;
}
return INVALID_TRACKDIR;
}
/**
* Check whether a ship should reverse to reach its destination.
* Called when leaving depot.
* @param v Ship.
* @param trackdir [out] the best of all possible reversed trackdirs.
* @return true if the reverse direction is better.
*/
static bool CheckShipReverse(const Ship *v, Trackdir *trackdir)
{
bool path_found = false;
ShipPathCache dummy_cache;
Trackdir best_origin_dir = INVALID_TRACKDIR;
if (trackdir == nullptr) {
/* The normal case, typically called when ships leave a dock. */
const Trackdir reverse_dir = ReverseTrackdir(v->GetVehicleTrackdir());
const TrackdirBits forward_dirs = TrackdirToTrackdirBits(v->GetVehicleTrackdir());
const TrackdirBits reverse_dirs = TrackdirToTrackdirBits(reverse_dir);
(void)ChooseShipTrack(v, v->tile, forward_dirs, reverse_dirs, path_found, dummy_cache, best_origin_dir);
return path_found && best_origin_dir == reverse_dir;
} else {
/* This gets called when a ship suddenly can't move forward, e.g. due to terraforming. */
const DiagDirection entry = ReverseDiagDir(VehicleExitDir(v->direction, v->state));
const TrackdirBits reverse_dirs = DiagdirReachesTrackdirs(entry) & TrackStatusToTrackdirBits(GetTileTrackStatus(v->tile, TRANSPORT_WATER, 0, entry));
(void)ChooseShipTrack(v, v->tile, TRACKDIR_BIT_NONE, reverse_dirs, path_found, dummy_cache, best_origin_dir);
*trackdir = path_found && best_origin_dir != INVALID_TRACKDIR ? best_origin_dir : GetRandomTrackdir(reverse_dirs);
return true;
}
NOT_REACHED();
}
};
@ -424,16 +397,27 @@ struct CYapfShip : CYapfT<CYapfShip_TypesT<CYapfShip, CFollowTrackWater, CShipNo
explicit CYapfShip(int max_nodes) { this->max_search_nodes = max_nodes; }
};
/** Ship controller helper - path finder invoker. */
Track YapfShipChooseTrack(const Ship *v, TileIndex tile, bool &path_found, ShipPathCache &path_cache)
Trackdir YapfShipChooseTrack(const Ship *v, bool &path_found, ShipPathCache &path_cache)
{
Trackdir best_origin_dir = INVALID_TRACKDIR;
const TrackdirBits origin_dirs = TrackdirToTrackdirBits(v->GetVehicleTrackdir());
const Trackdir td_ret = CYapfShip::ChooseShipTrack(v, tile, origin_dirs, TRACKDIR_BIT_NONE, path_found, path_cache, best_origin_dir);
return (td_ret != INVALID_TRACKDIR) ? TrackdirToTrack(td_ret) : INVALID_TRACK;
/* This is always called when a ship is about to exit a tile, hence we add up to three reverse directions. */
const TrackdirBits forward_dirs = TrackdirToTrackdirBits(v->GetVehicleTrackdir());
const TrackdirBits all_water_dirs = TrackStatusToTrackdirBits(GetTileTrackStatus(v->tile, TRANSPORT_WATER, 0));
const TrackdirBits reverse_dirs = all_water_dirs & DiagdirReachesTrackdirs(ReverseDiagDir(TrackdirToExitdir(v->GetVehicleTrackdir())));
const Trackdir trackdir = CYapfShip::ChooseShipTrack(v, forward_dirs, reverse_dirs, path_found, path_cache);
assert(trackdir != INVALID_TRACKDIR);
return trackdir;
}
bool YapfShipCheckReverse(const Ship *v, Trackdir *trackdir)
bool YapfShipCheckReverse(const Ship *v, Trackdir &trackdir)
{
return CYapfShip::CheckShipReverse(v, trackdir);
/* It is not clear where the ship is within its current track, so we only add the forward (current) and reverse direction. */
const TrackdirBits forward_dirs = TrackdirToTrackdirBits(v->GetVehicleTrackdir());
const TrackdirBits reverse_dirs = TrackdirToTrackdirBits(ReverseTrackdir(v->GetVehicleTrackdir()));
bool path_found = false;
ShipPathCache dummy_cache;
trackdir = CYapfShip::ChooseShipTrack(v, forward_dirs, reverse_dirs, path_found, dummy_cache);
assert(trackdir != INVALID_TRACKDIR);
return path_found && HasBit(reverse_dirs, trackdir);
}

View File

@ -367,7 +367,7 @@ static Vehicle *EnsureNoMovingShipProc(Vehicle *v, void *)
return v->type == VEH_SHIP && v->cur_speed != 0 ? v : nullptr;
}
static bool CheckReverseShip(const Ship *v, Trackdir *trackdir = nullptr)
static bool CheckReverseShip(const Ship *v, Trackdir &trackdir)
{
/* Ask pathfinder for best direction */
return YapfShipCheckReverse(v, trackdir);
@ -405,7 +405,8 @@ static bool CheckShipLeaveDepot(Ship *v)
TrackBits north_tracks = DiagdirReachesTracks(north_dir) & GetTileShipTrackStatus(north_neighbour);
TrackBits south_tracks = DiagdirReachesTracks(south_dir) & GetTileShipTrackStatus(south_neighbour);
if (north_tracks && south_tracks) {
if (CheckReverseShip(v)) north_tracks = TRACK_BIT_NONE;
Trackdir dummy_trackdir;
if (CheckReverseShip(v, dummy_trackdir)) north_tracks = TRACK_BIT_NONE;
}
if (north_tracks) {
@ -488,54 +489,39 @@ static void ShipArrivesAt(const Vehicle *v, Station *st)
* Runs the pathfinder to choose a track to continue along.
*
* @param v Ship to navigate
* @param tile Tile, the ship is about to enter
* @param tracks Available track choices on \a tile
* @return Track to choose, or INVALID_TRACK when to reverse.
* @param trackdirs Available trackdir choices on the vehicle's type
* @return Trackdir to choose
*/
static Track ChooseShipTrack(Ship *v, TileIndex tile, TrackBits tracks)
static Trackdir ChooseShipTrack(Ship *v, TrackdirBits trackdirs)
{
bool path_found = true;
Track track;
Trackdir trackdir;
if (v->dest_tile == 0) {
/* No destination, don't invoke pathfinder. */
track = TrackBitsToTrack(v->state);
if (!IsDiagonalTrack(track)) track = TrackToOppositeTrack(track);
if (!HasBit(tracks, track)) track = FindFirstTrack(tracks);
trackdir = FindFirstTrackdir(trackdirs);
if (trackdir == INVALID_TRACKDIR) trackdir = ReverseTrackdir(v->GetVehicleTrackdir());
path_found = false;
} else {
/* Attempt to follow cached path. */
if (!v->path.empty()) {
track = TrackdirToTrack(v->path.back().trackdir);
trackdir = v->path.back().trackdir;
if (HasBit(tracks, track)) {
if (HasBit(trackdirs, trackdir)) {
v->path.pop_back();
/* HandlePathfindResult() is not called here because this is not a new pathfinder result. */
return track;
return trackdir;
}
/* Cached path is invalid so continue with pathfinder. */
v->path.clear();
}
track = YapfShipChooseTrack(v, tile, path_found, v->path);
trackdir = YapfShipChooseTrack(v, path_found, v->path);
}
v->HandlePathfindingResult(path_found);
return track;
}
/**
* Get the available water tracks on a tile for a ship entering a tile.
* @param tile The tile about to enter.
* @param dir The entry direction.
* @return The available trackbits on the next tile.
*/
static inline TrackBits GetAvailShipTracks(TileIndex tile, DiagDirection dir)
{
TrackBits tracks = GetTileShipTrackStatus(tile) & DiagdirReachesTracks(dir);
return tracks;
return trackdir;
}
/** Structure for ship sub-coordinate data for moving into a new tile via a Diagdir onto a Track. */
@ -705,7 +691,8 @@ static void ShipController(Ship *v)
if (v->vehstatus & VS_STOPPED) return;
if (ProcessOrders(v) && CheckReverseShip(v)) return ReverseShip(v);
Trackdir best_reverse_dir = INVALID_TRACKDIR;
if (ProcessOrders(v) && CheckReverseShip(v, best_reverse_dir)) return ReverseShipIntoTrackdir(v, best_reverse_dir);
v->HandleLoading();
@ -793,19 +780,14 @@ static void ShipController(Ship *v)
const DiagDirection diagdir = DiagdirBetweenTiles(gp.old_tile, gp.new_tile);
assert(diagdir != INVALID_DIAGDIR);
const TrackBits tracks = GetAvailShipTracks(gp.new_tile, diagdir);
if (tracks == TRACK_BIT_NONE) {
Trackdir trackdir = INVALID_TRACKDIR;
CheckReverseShip(v, &trackdir);
if (trackdir == INVALID_TRACKDIR) return ReverseShip(v);
return ReverseShipIntoTrackdir(v, trackdir);
}
const TrackdirBits forward_trackdirs = TrackBitsToTrackdirBits(GetTileShipTrackStatus(gp.new_tile)) & DiagdirReachesTrackdirs(diagdir);
/* Choose a direction, and continue if we find one */
const Track track = ChooseShipTrack(v, gp.new_tile, tracks);
if (track == INVALID_TRACK) return ReverseShip(v);
/* Choose a direction, which might require a reversal */
const Trackdir new_trackdir = ChooseShipTrack(v, forward_trackdirs);
assert(new_trackdir != INVALID_TRACKDIR);
if (!HasBit(forward_trackdirs, new_trackdir)) return ReverseShipIntoTrackdir(v, new_trackdir);
const ShipSubcoordData &b = _ship_subcoord[diagdir][track];
const ShipSubcoordData &b = _ship_subcoord[diagdir][TrackdirToTrack(new_trackdir)];
gp.x = (gp.x & ~0xF) | b.x_subcoord;
gp.y = (gp.y & ~0xF) | b.y_subcoord;
@ -816,7 +798,7 @@ static void ShipController(Ship *v)
if (!HasBit(r, VETS_ENTERED_WORMHOLE)) {
v->tile = gp.new_tile;
v->state = TrackToTrackBits(track);
v->state = TrackToTrackBits(TrackdirToTrack(new_trackdir));
/* Update ship cache when the water class changes. Aqueducts are always canals. */
if (GetEffectiveWaterClass(gp.old_tile) != GetEffectiveWaterClass(gp.new_tile)) v->UpdateCache();