From 693fe8839648eb55cc8fa880e26102794024fb9f Mon Sep 17 00:00:00 2001 From: Mauller <26652186+Mauller@users.noreply.github.com> Date: Tue, 9 Dec 2025 21:11:52 +0000 Subject: [PATCH] bugfix(pathfinder): Prevent infinite loop within PathfindCell::putOnSortedOpenList() --- .../Source/GameLogic/AI/AIPathfind.cpp | 38 +++++++++++------- .../Source/GameLogic/AI/AIPathfind.cpp | 39 ++++++++++++------- 2 files changed, 48 insertions(+), 29 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp b/Generals/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp index 2b693ac9504..f0f4ca98dce 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp @@ -92,6 +92,13 @@ inline Int IABS(Int x) { if (x>=0) return x; return -x;}; //----------------------------------------------------------------------------------- static Int frameToShowObstacles; +constexpr const UnsignedInt MAX_CELL_COUNT = 500; +constexpr const UnsignedInt MAX_ADJUSTMENT_CELL_COUNT = 400; +constexpr const UnsignedInt MAX_SAFE_PATH_CELL_COUNT = 2000; + +constexpr const UnsignedInt PATHFIND_CELLS_PER_FRAME = 5000; // Number of cells we will search pathfinding per frame. +constexpr const UnsignedInt CELL_INFOS_TO_ALLOCATE = 30000; + //----------------------------------------------------------------------------------- PathNode::PathNode() : m_nextOpti(0), @@ -1077,8 +1084,6 @@ Real Path::computeFlightDistToGoal( const Coord3D *pos, Coord3D& goalPos ) } //----------------------------------------------------------------------------------- -enum { PATHFIND_CELLS_PER_FRAME=5000}; // Number of cells we will search pathfinding per frame. -enum {CELL_INFOS_TO_ALLOCATE = 30000}; PathfindCellInfo *PathfindCellInfo::s_infoArray = NULL; PathfindCellInfo *PathfindCellInfo::s_firstFree = NULL; @@ -1547,8 +1552,18 @@ PathfindCell *PathfindCell::putOnSortedOpenList( PathfindCell *list ) { // insertion sort PathfindCell *c, *lastCell = NULL; - for( c = list; c; c = c->getNextOpen() ) +#if RETAIL_COMPATIBLE_PATHFINDING + // TheSuperHackers @bugfix In the retail compatible pathfinding, on rare ocassions, we get stuck in an infinite loop + // External code should pickup on the bad behaviour and cleanup properly, but we need to explicitly break out here + // The fixed pathfinding does not have this issue due to the proper cleanup of pathfindCells and their pathfindCellInfos + UnsignedInt cellCount = 0; + for (c = list; c && cellCount < PATHFIND_CELLS_PER_FRAME; c = c->getNextOpen()) + { + cellCount++; +#else + for (c = list; c; c = c->getNextOpen()) { +#endif if (c->m_info->m_totalCost > m_info->m_totalCost) break; @@ -4715,8 +4730,7 @@ Bool Pathfinder::adjustToLandingDestination(Object *obj, Coord3D *dest) } worldToCell( &adjustDest, &cell ); - enum {MAX_CELLS_TO_TRY=400}; - Int limit = MAX_CELLS_TO_TRY; + Int limit = MAX_ADJUSTMENT_CELL_COUNT; Int i, j; i = cell.x; j = cell.y; @@ -4793,8 +4807,7 @@ Bool Pathfinder::adjustDestination(Object *obj, const LocomotorSet& locomotorSet layer = TheTerrainLogic->getLayerForDestination(groupDest); } - enum {MAX_CELLS_TO_TRY=400}; - Int limit = MAX_CELLS_TO_TRY; + Int limit = MAX_ADJUSTMENT_CELL_COUNT; Int i, j; i = cell.x; j = cell.y; @@ -4877,8 +4890,8 @@ Bool Pathfinder::adjustTargetDestination(const Object *obj, const Object *target if (worldToCell( &adjustDest, &cell )) { return false; // outside of bounds. } - enum {MAX_CELLS_TO_TRY=400}; - Int limit = MAX_CELLS_TO_TRY; + + Int limit = MAX_ADJUSTMENT_CELL_COUNT; Int i, j; i = cell.x; j = cell.y; @@ -5001,8 +5014,7 @@ Bool Pathfinder::adjustToPossibleDestination(Object *obj, const LocomotorSet& lo } } - enum {MAX_CELLS_TO_TRY=400}; - Int limit = MAX_CELLS_TO_TRY; + Int limit = MAX_ADJUSTMENT_CELL_COUNT; Int i, j; i = goalCellNdx.x; j = goalCellNdx.y; @@ -7511,7 +7523,6 @@ Bool Pathfinder::pathDestination( Object *obj, const LocomotorSet& locomotorSet if (!obj) return false; Int cellCount = 0; -#define MAX_CELL_COUNT 500 Coord3D adjustTo = *groupDest; Coord3D *to = &adjustTo; @@ -7806,7 +7817,6 @@ Int Pathfinder::checkPathCost(Object *obj, const LocomotorSet& locomotorSet, con if (!obj) return MAX_COST; Int cellCount = 0; -#define MAX_CELL_COUNT 500 Coord3D adjustTo = *rawTo; Coord3D *to = &adjustTo; @@ -10328,7 +10338,7 @@ Path *Pathfinder::findSafePath( const Object *obj, const LocomotorSet& locomotor // Int startTimeMS = ::GetTickCount(); #endif - const Int MAX_CELLS = 2000; // this is a rather expensive operation, so limit the search. + const Int MAX_CELLS = MAX_SAFE_PATH_CELL_COUNT; // this is a rather expensive operation, so limit the search. Bool centerInCell; Int radius; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp index e1b50033c25..bc2eb2aed11 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp @@ -106,7 +106,13 @@ inline Int IABS(Int x) { if (x>=0) return x; return -x;}; static Int frameToShowObstacles; -static UnsignedInt ZONE_UPDATE_FREQUENCY = 300; +constexpr const UnsignedInt ZONE_UPDATE_FREQUENCY = 300; +constexpr const UnsignedInt MAX_CELL_COUNT = 500; +constexpr const UnsignedInt MAX_ADJUSTMENT_CELL_COUNT = 400; +constexpr const UnsignedInt MAX_SAFE_PATH_CELL_COUNT = 2000; + +constexpr const UnsignedInt PATHFIND_CELLS_PER_FRAME = 5000; // Number of cells we will search pathfinding per frame. +constexpr const UnsignedInt CELL_INFOS_TO_ALLOCATE = 30000; //----------------------------------------------------------------------------------- PathNode::PathNode() : @@ -1093,8 +1099,6 @@ Real Path::computeFlightDistToGoal( const Coord3D *pos, Coord3D& goalPos ) } //----------------------------------------------------------------------------------- -enum { PATHFIND_CELLS_PER_FRAME=5000}; // Number of cells we will search pathfinding per frame. -enum {CELL_INFOS_TO_ALLOCATE = 30000}; PathfindCellInfo *PathfindCellInfo::s_infoArray = NULL; PathfindCellInfo *PathfindCellInfo::s_firstFree = NULL; @@ -1565,8 +1569,18 @@ PathfindCell *PathfindCell::putOnSortedOpenList( PathfindCell *list ) { // insertion sort PathfindCell *c, *lastCell = NULL; - for( c = list; c; c = c->getNextOpen() ) +#if RETAIL_COMPATIBLE_PATHFINDING + // TheSuperHackers @bugfix In the retail compatible pathfinding, on rare ocassions, we get stuck in an infinite loop + // External code should pickup on the bad behaviour and cleanup properly, but we need to explicitly break out here + // The fixed pathfinding does not have this issue due to the proper cleanup of pathfindCells and their pathfindCellInfos + UnsignedInt cellCount = 0; + for (c = list; c && cellCount < PATHFIND_CELLS_PER_FRAME; c = c->getNextOpen()) { + cellCount++; +#else + for (c = list; c; c = c->getNextOpen()) + { +#endif if (c->m_info->m_totalCost > m_info->m_totalCost) break; @@ -4998,8 +5012,7 @@ Bool Pathfinder::adjustToLandingDestination(Object *obj, Coord3D *dest) } worldToCell( &adjustDest, &cell ); - enum {MAX_CELLS_TO_TRY=400}; - Int limit = MAX_CELLS_TO_TRY; + Int limit = MAX_ADJUSTMENT_CELL_COUNT; Int i, j; i = cell.x; j = cell.y; @@ -5076,8 +5089,7 @@ Bool Pathfinder::adjustDestination(Object *obj, const LocomotorSet& locomotorSet layer = TheTerrainLogic->getLayerForDestination(groupDest); } - enum {MAX_CELLS_TO_TRY=400}; - Int limit = MAX_CELLS_TO_TRY; + Int limit = MAX_ADJUSTMENT_CELL_COUNT; Int i, j; i = cell.x; j = cell.y; @@ -5160,8 +5172,8 @@ Bool Pathfinder::adjustTargetDestination(const Object *obj, const Object *target if (worldToCell( &adjustDest, &cell )) { return false; // outside of bounds. } - enum {MAX_CELLS_TO_TRY=400}; - Int limit = MAX_CELLS_TO_TRY; + + Int limit = MAX_ADJUSTMENT_CELL_COUNT; Int i, j; i = cell.x; j = cell.y; @@ -5284,8 +5296,7 @@ Bool Pathfinder::adjustToPossibleDestination(Object *obj, const LocomotorSet& lo } } - enum {MAX_CELLS_TO_TRY=400}; - Int limit = MAX_CELLS_TO_TRY; + Int limit = MAX_ADJUSTMENT_CELL_COUNT; Int i, j; i = goalCellNdx.x; j = goalCellNdx.y; @@ -7910,7 +7921,6 @@ Bool Pathfinder::pathDestination( Object *obj, const LocomotorSet& locomotorSet if (!obj) return false; Int cellCount = 0; -#define MAX_CELL_COUNT 500 Coord3D adjustTo = *groupDest; Coord3D *to = &adjustTo; @@ -8205,7 +8215,6 @@ Int Pathfinder::checkPathCost(Object *obj, const LocomotorSet& locomotorSet, con if (!obj) return MAX_COST; Int cellCount = 0; -#define MAX_CELL_COUNT 500 Coord3D adjustTo = *rawTo; Coord3D *to = &adjustTo; @@ -10805,7 +10814,7 @@ Path *Pathfinder::findSafePath( const Object *obj, const LocomotorSet& locomotor // Int startTimeMS = ::GetTickCount(); #endif - const Int MAX_CELLS = 2000; // this is a rather expensive operation, so limit the search. + const Int MAX_CELLS = MAX_SAFE_PATH_CELL_COUNT; // this is a rather expensive operation, so limit the search. Bool centerInCell; Int radius;