From 0e75dfd49fc98fc8366dc0f05ccb31dfc523127f Mon Sep 17 00:00:00 2001 From: Rubidium Date: Sun, 12 May 2024 14:04:35 +0200 Subject: [PATCH] Codechange: migrate aystar to use YAPF's nodelist infrastructure --- src/landscape.cpp | 49 +++------ src/pathfinder/aystar.cpp | 179 +++++-------------------------- src/pathfinder/aystar.h | 63 ++++------- src/pathfinder/pathfinder_type.h | 1 - src/settings_table.cpp | 1 + 5 files changed, 64 insertions(+), 229 deletions(-) diff --git a/src/landscape.cpp b/src/landscape.cpp index 9abadda591..9065844904 100644 --- a/src/landscape.cpp +++ b/src/landscape.cpp @@ -1201,48 +1201,48 @@ struct River_UserData { }; /* AyStar callback for checking whether we reached our destination. */ -static int32_t River_EndNodeCheck(const AyStar *aystar, const OpenListNode *current) +static int32_t River_EndNodeCheck(const AyStar *aystar, const PathNode *current) { - return current->path.node.tile == *(TileIndex*)aystar->user_target ? AYSTAR_FOUND_END_NODE : AYSTAR_DONE; + return current->GetTile() == *(TileIndex*)aystar->user_target ? AYSTAR_FOUND_END_NODE : AYSTAR_DONE; } /* AyStar callback for getting the cost of the current node. */ -static int32_t River_CalculateG(AyStar *, AyStarNode *, OpenListNode *) +static int32_t River_CalculateG(AyStar *, AyStarNode *, PathNode *) { return 1 + RandomRange(_settings_game.game_creation.river_route_random); } /* AyStar callback for getting the estimated cost to the destination. */ -static int32_t River_CalculateH(AyStar *aystar, AyStarNode *current, OpenListNode *) +static int32_t River_CalculateH(AyStar *aystar, AyStarNode *current, PathNode *) { - return DistanceManhattan(*(TileIndex*)aystar->user_target, current->tile); + return DistanceManhattan(*(TileIndex*)aystar->user_target, current->m_tile); } /* AyStar callback for getting the neighbouring nodes of the given node. */ -static void River_GetNeighbours(AyStar *aystar, OpenListNode *current) +static void River_GetNeighbours(AyStar *aystar, PathNode *current) { - TileIndex tile = current->path.node.tile; + TileIndex tile = current->GetTile(); aystar->num_neighbours = 0; for (DiagDirection d = DIAGDIR_BEGIN; d < DIAGDIR_END; d++) { TileIndex t2 = tile + TileOffsByDiagDir(d); if (IsValidTile(t2) && FlowsDown(tile, t2)) { - aystar->neighbours[aystar->num_neighbours].tile = t2; - aystar->neighbours[aystar->num_neighbours].direction = INVALID_TRACKDIR; + aystar->neighbours[aystar->num_neighbours].m_tile = t2; + aystar->neighbours[aystar->num_neighbours].m_td = INVALID_TRACKDIR; aystar->num_neighbours++; } } } /* AyStar callback when an route has been found. */ -static void River_FoundEndNode(AyStar *aystar, OpenListNode *current) +static void River_FoundEndNode(AyStar *aystar, PathNode *current) { River_UserData *data = (River_UserData *)aystar->user_data; /* First, build the river without worrying about its width. */ uint cur_pos = 0; - for (PathNode *path = ¤t->path; path != nullptr; path = path->parent, cur_pos++) { - TileIndex tile = path->node.tile; + for (PathNode *path = current->m_parent; path != nullptr; path = path->m_parent, cur_pos++) { + TileIndex tile = path->GetTile(); if (!IsWaterTile(tile)) { MakeRiverAndModifyDesertZoneAround(tile); } @@ -1257,30 +1257,18 @@ static void River_FoundEndNode(AyStar *aystar, OpenListNode *current) uint radius; cur_pos = 0; - for (PathNode *path = ¤t->path; path != nullptr; path = path->parent, cur_pos++) { - TileIndex tile = path->node.tile; + for (PathNode *path = current->m_parent; path != nullptr; path = path->m_parent, cur_pos++) { + TileIndex tile = path->GetTile(); /* Check if we should widen river depending on how far we are away from the source. */ current_river_length = DistanceManhattan(data->spring, tile); radius = std::min(3u, (current_river_length / (long_river_length / 3u)) + 1u); - if (radius > 1) CircularTileSearch(&tile, radius, RiverMakeWider, (void *)&path->node.tile); + if (radius > 1) CircularTileSearch(&tile, radius, RiverMakeWider, (void *)&path->m_key.m_tile); } } } -static const uint RIVER_HASH_SIZE = 8; ///< The number of bits the hash for river finding should have. - -/** - * Simple hash function for river tiles to be used by AyStar. - * @param tile The tile to hash. - * @return The hash for the tile. - */ -static uint River_Hash(TileIndex tile, Trackdir) -{ - return GB(TileHash(TileX(tile), TileY(tile)), 0, RIVER_HASH_SIZE); -} - /** * Actually build the river between the begin and end tiles using AyStar. * @param begin The begin of the river. @@ -1301,14 +1289,11 @@ static void BuildRiver(TileIndex begin, TileIndex end, TileIndex spring, bool ma finder.user_target = &end; finder.user_data = &user_data; - finder.Init(River_Hash, 1 << RIVER_HASH_SIZE); - AyStarNode start; - start.tile = begin; - start.direction = INVALID_TRACKDIR; + start.m_tile = begin; + start.m_td = INVALID_TRACKDIR; finder.AddStartNode(&start, 0); finder.Main(); - finder.Free(); } /** diff --git a/src/pathfinder/aystar.cpp b/src/pathfinder/aystar.cpp index 5246731300..711f7c0467 100644 --- a/src/pathfinder/aystar.cpp +++ b/src/pathfinder/aystar.cpp @@ -13,69 +13,11 @@ * http://en.wikipedia.org/wiki/A-star_search_algorithm. */ -/* - * Friendly reminder: - * Call (AyStar).free() when you are done with Aystar. It reserves a lot of memory - * And when not free'd, it can cause system-crashes. - * Also remember that when you stop an algorithm before it is finished, your - * should call clear() yourself! - */ - #include "../../stdafx.h" -#include "../../core/alloc_func.hpp" #include "aystar.h" #include "../../safeguards.h" -/** - * This looks in the hash whether a node exists in the closed list. - * @param node Node to search. - * @return The #PathNode if it is available, else \c nullptr - */ -PathNode *AyStar::ClosedListIsInList(const AyStarNode *node) -{ - return (PathNode*)this->closedlist_hash.Get(node->tile, node->direction); -} - -/** - * This adds a node to the closed list. - * It makes a copy of the data. - * @param node Node to add to the closed list. - */ -void AyStar::ClosedListAdd(const PathNode *node) -{ - /* Add a node to the ClosedList */ - PathNode *new_node = MallocT(1); - *new_node = *node; - this->closedlist_hash.Set(node->node.tile, node->node.direction, new_node); -} - -/** - * Check whether a node is in the open list. - * @param node Node to search. - * @return If the node is available, it is returned, else \c nullptr is returned. - */ -OpenListNode *AyStar::OpenListIsInList(const AyStarNode *node) -{ - return (OpenListNode*)this->openlist_hash.Get(node->tile, node->direction); -} - -/** - * Gets the best node from the open list. - * It deletes the returned node from the open list. - * @returns the best node available, or \c nullptr of none is found. - */ -OpenListNode *AyStar::OpenListPop() -{ - /* Return the item the Queue returns.. the best next OpenList item. */ - OpenListNode *res = (OpenListNode*)this->openlist_queue.Pop(); - if (res != nullptr) { - this->openlist_hash.DeleteValue(res->path.node.tile, res->path.node.direction); - } - - return res; -} - /** * Adds a node to the open list. * It makes a copy of node, and puts the pointer of parent in the struct. @@ -83,61 +25,55 @@ OpenListNode *AyStar::OpenListPop() void AyStar::OpenListAdd(PathNode *parent, const AyStarNode *node, int f, int g) { /* Add a new Node to the OpenList */ - OpenListNode *new_node = MallocT(1); - new_node->g = g; - new_node->path.parent = parent; - new_node->path.node = *node; - this->openlist_hash.Set(node->tile, node->direction, new_node); - - /* Add it to the queue */ - this->openlist_queue.Push(new_node, f); + PathNode *new_node = this->nodes.CreateNewNode(); + new_node->Set(parent, node->m_tile, node->m_td, true); + new_node->m_estimate = f; + new_node->m_cost = g; + this->nodes.InsertOpenNode(*new_node); } /** * Checks one tile and calculate its f-value */ -void AyStar::CheckTile(AyStarNode *current, OpenListNode *parent) +void AyStar::CheckTile(AyStarNode *current, PathNode *parent) { - int new_f, new_g, new_h; - PathNode *closedlist_parent; - OpenListNode *check; - /* Check the new node against the ClosedList */ - if (this->ClosedListIsInList(current) != nullptr) return; + if (this->nodes.FindClosedNode(*current) != nullptr) return; /* Calculate the G-value for this node */ - new_g = this->CalculateG(this, current, parent); + int new_g = this->CalculateG(this, current, parent); /* If the value was INVALID_NODE, we don't do anything with this node */ if (new_g == AYSTAR_INVALID_NODE) return; /* There should not be given any other error-code.. */ assert(new_g >= 0); /* Add the parent g-value to the new g-value */ - new_g += parent->g; - if (this->max_path_cost != 0 && (uint)new_g > this->max_path_cost) return; + new_g += parent->m_cost; + if (this->max_path_cost != 0 && new_g > this->max_path_cost) return; /* Calculate the h-value */ - new_h = this->CalculateH(this, current, parent); + int new_h = this->CalculateH(this, current, parent); /* There should not be given any error-code.. */ assert(new_h >= 0); /* The f-value if g + h */ - new_f = new_g + new_h; + int new_f = new_g + new_h; /* Get the pointer to the parent in the ClosedList (the current one is to a copy of the one in the OpenList) */ - closedlist_parent = this->ClosedListIsInList(&parent->path.node); + PathNode *closedlist_parent = this->nodes.FindClosedNode(parent->m_key); /* Check if this item is already in the OpenList */ - check = this->OpenListIsInList(current); + PathNode *check = this->nodes.FindOpenNode(*current); if (check != nullptr) { /* Yes, check if this g value is lower.. */ - if (new_g > check->g) return; - this->openlist_queue.Delete(check, 0); + if (new_g > check->m_cost) return; + this->nodes.PopOpenNode(check->m_key); /* It is lower, so change it to this item */ - check->g = new_g; - check->path.parent = closedlist_parent; + check->m_estimate = new_f; + check->m_cost = new_g; + check->m_parent = closedlist_parent; /* Re-add it in the openlist_queue. */ - this->openlist_queue.Push(check, new_f); + this->nodes.InsertOpenNode(*check); } else { /* A new node, add it to the OpenList */ this->OpenListAdd(closedlist_parent, current, new_f, new_g); @@ -155,38 +91,32 @@ void AyStar::CheckTile(AyStarNode *current, OpenListNode *parent) */ int AyStar::Loop() { - int i; - /* Get the best node from OpenList */ - OpenListNode *current = this->OpenListPop(); + PathNode *current = this->nodes.PopBestOpenNode(); /* If empty, drop an error */ if (current == nullptr) return AYSTAR_EMPTY_OPENLIST; /* Check for end node and if found, return that code */ - if (this->EndNodeCheck(this, current) == AYSTAR_FOUND_END_NODE && (¤t->path)->parent != nullptr) { + if (this->EndNodeCheck(this, current) == AYSTAR_FOUND_END_NODE && current->m_parent != nullptr) { if (this->FoundEndNode != nullptr) { this->FoundEndNode(this, current); } - free(current); return AYSTAR_FOUND_END_NODE; } /* Add the node to the ClosedList */ - this->ClosedListAdd(¤t->path); + this->nodes.InsertClosedNode(*current); /* Load the neighbours */ this->GetNeighbours(this, current); /* Go through all neighbours */ - for (i = 0; i < this->num_neighbours; i++) { + for (int i = 0; i < this->num_neighbours; i++) { /* Check and add them to the OpenList if needed */ this->CheckTile(&this->neighbours[i], current); } - /* Free the node */ - free(current); - - if (this->max_search_nodes != 0 && this->closedlist_hash.GetSize() >= this->max_search_nodes) { + if (this->max_search_nodes != 0 && this->nodes.ClosedCount() >= this->max_search_nodes) { /* We've expanded enough nodes */ return AYSTAR_LIMIT_REACHED; } else { @@ -195,39 +125,6 @@ int AyStar::Loop() } } -/** - * This function frees the memory it allocated - */ -void AyStar::Free() -{ - this->openlist_queue.Free(false); - /* 2nd argument above is false, below is true, to free the values only - * once */ - this->openlist_hash.Delete(true); - this->closedlist_hash.Delete(true); -#ifdef AYSTAR_DEBUG - Debug(misc, 0, "[AyStar] Memory free'd"); -#endif -} - -/** - * This function make the memory go back to zero. - * This function should be called when you are using the same instance again. - */ -void AyStar::Clear() -{ - /* Clean the Queue, but not the elements within. That will be done by - * the hash. */ - this->openlist_queue.Clear(false); - /* Clean the hashes */ - this->openlist_hash.Clear(true); - this->closedlist_hash.Clear(true); - -#ifdef AYSTAR_DEBUG - Debug(misc, 0, "[AyStar] Cleared AyStar"); -#endif -} - /** * This is the function you call to run AyStar. * @return Possible values: @@ -251,10 +148,6 @@ int AyStar::Main() default: break; } #endif - if (r != AYSTAR_STILL_BUSY) { - /* We're done, clean up */ - this->Clear(); - } switch (r) { case AYSTAR_FOUND_END_NODE: return AYSTAR_FOUND_END_NODE; @@ -272,7 +165,7 @@ int AyStar::Main() * @param start_node Node to start with. * @param g the cost for starting with this node. */ -void AyStar::AddStartNode(AyStarNode *start_node, uint g) +void AyStar::AddStartNode(AyStarNode *start_node, int g) { #ifdef AYSTAR_DEBUG Debug(misc, 0, "[AyStar] Starting A* Algorithm from node ({}, {}, {})\n", @@ -280,23 +173,3 @@ void AyStar::AddStartNode(AyStarNode *start_node, uint g) #endif this->OpenListAdd(nullptr, start_node, 0, g); } - -/** - * Initialize an #AyStar. You should fill all appropriate fields before - * calling #Init (see the declaration of #AyStar for which fields are internal). - */ -void AyStar::Init(Hash_HashProc hash, uint num_buckets) -{ - /* Allocated the Hash for the OpenList and ClosedList */ - this->openlist_hash.Init(hash, num_buckets); - this->closedlist_hash.Init(hash, num_buckets); - - /* Set up our sorting queue - * BinaryHeap allocates a block of 1024 nodes - * When that one gets full it reserves another one, till this number - * That is why it can stay this high */ - this->openlist_queue.Init(102400); - - /* Set a reasonable default limit */ - this->max_search_nodes = AYSTAR_DEF_MAX_SEARCH_NODES; -} diff --git a/src/pathfinder/aystar.h b/src/pathfinder/aystar.h index c157259a3e..d375a052a5 100644 --- a/src/pathfinder/aystar.h +++ b/src/pathfinder/aystar.h @@ -16,9 +16,14 @@ #ifndef AYSTAR_H #define AYSTAR_H -#include "queue.h" -#include "../../tile_type.h" -#include "../../track_type.h" +#include "../track_func.h" + +#include "../misc/hashtable.hpp" +#include "../misc/binaryheap.hpp" +#include "../misc/dbg_helpers.h" + +#include "yapf/nodelist.hpp" +#include "yapf/yapf_node.hpp" static const int AYSTAR_DEF_MAX_SEARCH_NODES = 10000; ///< Reference limit for #AyStar::max_search_nodes @@ -34,26 +39,9 @@ enum AystarStatus { static const int AYSTAR_INVALID_NODE = -1; ///< Item is not valid (for example, not walkable). -/** Node in the search. */ -struct AyStarNode { - TileIndex tile; - Trackdir direction; -}; +using AyStarNode = CYapfNodeKeyTrackDir; -/** A path of nodes. */ -struct PathNode { - AyStarNode node; - PathNode *parent; ///< The parent of this item. -}; - -/** - * Internal node. - * @note We do not save the h-value, because it is only needed to calculate the f-value. - * h-value should \em always be the distance left to the end-tile. - */ -struct OpenListNode { - int g; - PathNode path; +struct PathNode : CYapfNodeT { }; bool CheckIgnoreFirstTile(const PathNode *node); @@ -74,7 +62,7 @@ struct AyStar; * - #AYSTAR_FOUND_END_NODE : indicates this is the end tile * - #AYSTAR_DONE : indicates this is not the end tile (or direction was wrong) */ -typedef int32_t AyStar_EndNodeCheck(const AyStar *aystar, const OpenListNode *current); +typedef int32_t AyStar_EndNodeCheck(const AyStar *aystar, const PathNode *current); /** * Calculate the G-value for the %AyStar algorithm. @@ -82,27 +70,27 @@ typedef int32_t AyStar_EndNodeCheck(const AyStar *aystar, const OpenListNode *cu * - #AYSTAR_INVALID_NODE : indicates an item is not valid (e.g.: unwalkable) * - Any value >= 0 : the g-value for this tile */ -typedef int32_t AyStar_CalculateG(AyStar *aystar, AyStarNode *current, OpenListNode *parent); +typedef int32_t AyStar_CalculateG(AyStar *aystar, AyStarNode *current, PathNode *parent); /** * Calculate the H-value for the %AyStar algorithm. * Mostly, this must return the distance (Manhattan way) between the current point and the end point. * @return The h-value for this tile (any value >= 0) */ -typedef int32_t AyStar_CalculateH(AyStar *aystar, AyStarNode *current, OpenListNode *parent); +typedef int32_t AyStar_CalculateH(AyStar *aystar, AyStarNode *current, PathNode *parent); /** * This function requests the tiles around the current tile and put them in #neighbours. * #neighbours is never reset, so if you are not using directions, just leave it alone. * @warning Never add more #neighbours than memory allocated for it. */ -typedef void AyStar_GetNeighbours(AyStar *aystar, OpenListNode *current); +typedef void AyStar_GetNeighbours(AyStar *aystar, PathNode *current); /** * If the End Node is found, this function is called. * It can do, for example, calculate the route and put that in an array. */ -typedef void AyStar_FoundEndNode(AyStar *aystar, OpenListNode *current); +typedef void AyStar_FoundEndNode(AyStar *aystar, PathNode *current); /** * %AyStar search algorithm struct. @@ -134,36 +122,25 @@ struct AyStar { void *user_data; uint8_t loops_per_tick; ///< How many loops are there called before Main() gives control back to the caller. 0 = until done. - uint max_path_cost; ///< If the g-value goes over this number, it stops searching, 0 = infinite. - uint max_search_nodes; ///< The maximum number of nodes that will be expanded, 0 = infinite. + int max_path_cost; ///< If the g-value goes over this number, it stops searching, 0 = infinite. + int max_search_nodes = AYSTAR_DEF_MAX_SEARCH_NODES; ///< The maximum number of nodes that will be expanded, 0 = infinite. /* These should be filled with the neighbours of a tile by * GetNeighbours */ AyStarNode neighbours[12]; uint8_t num_neighbours; - void Init(Hash_HashProc hash, uint num_buckets); - /* These will contain the methods for manipulating the AyStar. Only * Main() should be called externally */ - void AddStartNode(AyStarNode *start_node, uint g); + void AddStartNode(AyStarNode *start_node, int g); int Main(); int Loop(); - void Free(); - void Clear(); - void CheckTile(AyStarNode *current, OpenListNode *parent); + void CheckTile(AyStarNode *current, PathNode *parent); protected: - Hash closedlist_hash; ///< The actual closed list. - BinaryHeap openlist_queue; ///< The open queue. - Hash openlist_hash; ///< An extra hash to speed up the process of looking up an element in the open list. + CNodeList_HashTableT nodes; void OpenListAdd(PathNode *parent, const AyStarNode *node, int f, int g); - OpenListNode *OpenListIsInList(const AyStarNode *node); - OpenListNode *OpenListPop(); - - void ClosedListAdd(const PathNode *node); - PathNode *ClosedListIsInList(const AyStarNode *node); }; #endif /* AYSTAR_H */ diff --git a/src/pathfinder/pathfinder_type.h b/src/pathfinder/pathfinder_type.h index aadcdac8d2..93b2561d32 100644 --- a/src/pathfinder/pathfinder_type.h +++ b/src/pathfinder/pathfinder_type.h @@ -11,7 +11,6 @@ #define PATHFINDER_TYPE_H #include "../tile_type.h" -#include "aystar.h" /** Length (penalty) of one tile with YAPF */ static const int YAPF_TILE_LENGTH = 100; diff --git a/src/settings_table.cpp b/src/settings_table.cpp index 3ca66dd9ee..0fd874a38c 100644 --- a/src/settings_table.cpp +++ b/src/settings_table.cpp @@ -17,6 +17,7 @@ #include "network/network_func.h" #include "network/core/config.h" #include "pathfinder/pathfinder_type.h" +#include "pathfinder/aystar.h" #include "linkgraph/linkgraphschedule.h" #include "genworld.h" #include "train.h"